package de.wepner.texteditor.model.event;

import java.util.LinkedList;


/**
 * <p>Replicates the behaviour of the <code>event</code> keyword in C#.</p>
 * <p>An {@link Event} gathers the listeners and gets notified when the event
 * occurred and all listeners should be called.</p>
 * <p>Since an event should not be fired from outside an other object, the
 * event containing object should have a <code>private</code> reference to the
 * event object and a <code>public</code> reference to its {@link Event#accessor}.</p>
 * 
 * @author Richard Wepner
 * @param <TElement> the event containing class
 * @param <TParam> the callback parameters, e. g. event arguments
 */
public class Event<TElement, TParam> {
    
    LinkedList<EventListener<TElement, TParam>> listeners = new LinkedList<EventListener<TElement, TParam>>();
    
    private final TElement selfReference;
    
    /**
     * <p>The events accessor.</p>
     * <p>This object should be referenced by the event containing object as
     * <code>public</code>, so listeners can be added, without firing the event
     * from outside the event containing object.</p>
     */
    public final EventAccessor accessor = this.new EventAccessor();
    
    
    public Event(TElement selfReference) {
        this.selfReference = selfReference;
    }
    
    
    /**
     * <p>Calls all listeners registered to this event.</p>
     * 
     * @param event the event data
     */
    public void fireEvent(TParam event) {
        for (EventListener<TElement, TParam> listener : this.listeners) {
            listener.eventOccurred(this.selfReference, event);
        }
    }
    
    
    /**
     * <p>Adds an event listener.</p>
     * <p>If the listener is already registered for this event, nothing
     * happens.</p>
     * 
     * @param listener the listener
     */
    public void addListener(EventListener<TElement, TParam> listener) {
        if (listener != null && !Event.this.listeners.contains(listener)) {
            this.listeners.add(listener);
        }
    }
    
    /**
     * <p>Removes an event listener.</p>
     * <p>If the listener isn't registered for this event, nothing
     * happens.</p>
     * 
     * @param listener the listener
     */
    public void removeListener(EventListener<TElement, TParam> listener) {
        if (listener != null && Event.this.listeners.contains(listener)) {
            this.listeners.remove(listener);
        }
    }
    
    
    /**
     * <p>Restricts the access to the event to adding and removing
     * listeners.</p>
     * <p>The EventAccessor contains methods for {@link EventAccessor#addListener} adding and
     * {@link EventAccessor#removeListener} listeners to and from the event,
     * which themself call the corresponding Event methods.</p>
     * 
     * @author Richard Wepner
     */
    public class EventAccessor {
        
        /**
         * <p>Adds the given listener to the {@link Event}.</p>
         * 
         * @param listener the listener to be added
         * @see Event#addListener(EventListener)
         */
        public void addListener(EventListener<TElement, TParam> listener) {
            Event.this.addListener(listener);
        }
        
        /**
         * <p>Removes the given listener from the {@link Event}.</p>
         * 
         * @param listener the listener to be added
         * @see Event#removeListener(EventListener)
         */
        public void removeListener(EventListener<TElement, TParam> listener) {
            Event.this.removeListener(listener);
        }
    }
}
